#ifndef SIMULATION_H_
#define SIMULATION_H_

#include <cmath>

#include "../Commands/Queue.h"
#include "./Objects/GameObject.h"
#include "./Level/Level.h"

#include "../Common/Vec2d.h"

namespace GameLogic {

/**
 * Klasa odpowiada za logike rozgrywki, w szczegolnosci przeprowadzanie symulacji swiata przedstawionego
 * (i przemiany bohatera romantycznego, o ile takowy istnieje).
 *
 * Ponadto to Simulation jest klasa odpowiedzialna za propagowanie zmian zachodzacych w rozgrywce - zarowno
 * dla symulacji klienta, jak i serwera. W przypadku symulacji serwera okresowo rozsylany jest stan wszystkich
 * poruszonych od ostatniego rozsylania obiektow na planszy. W przypadku klienta, to wlasnie Simulation
 * odpowiada za informowanie serwera o wykonaniu przez gracza aktualizacji zachowania obiektu, np. rozpoczecie
 * ruchu badz strzelania.
 *
 */
class Simulation {
	Commands::Queue& command_queue_;
	bool is_server_;

	/*
	 * Czas jaki uplynal od ostatniej propagacji stanu gry
	 */
	long last_broadcast_;

	Level::Level* level_;

	Ogre::RaySceneQuery *ray_scene_query_;

	/* Przechowuje ostatnia znana pozycje kursora. */
	Vec2i last_mouse_position_;

	long now_;

	void simulateObject( Objects::GameObject* go, long dt );

	/*
	 * Zwraca true, jezeli adres tile'a jest legalny.
	 */
	bool isValidTileCoord( int x, int y ) {
		return (x >= 0) && (x < level_->getWidth() ) && (y >= 0 ) && ( y < level_->getHeight() );
	}

	/*
	 * Zwraca true, jezeli adres tile'a jest legalny.
	 */
	bool isValidTileCoord( const Vec2i& tile_coord ) {
		return isValidTileCoord( tile_coord.x, tile_coord.y );
	}

	/*
	 * Oblicza polozenie obiektu. Wspolrzedne tile'a w ktorym znajduje sie obiekt zapiswane
	 * sa w zmiennej tile.
	 */
	void getObjectPosition( Objects::GameObject* go, Vec2i& tile ) {
		const Vec2f& pos = go->getPosition();
		float tile_size = level_->getTileSize();
		//Wspolrzedne tila:
		tile.x = static_cast<int>( pos.x / tile_size );
		tile.y = static_cast<int>( pos.y / tile_size );
	}

	/*
	 * Oblicza polozenie obiektu i jego przesuniecie w zajmowanym tile'u. Wspolrzedne
	 * tile'a w ktorym znajduje sie obiekt zapiswane sa w zmiennej tile, zas przesuniecie
	 * wzgledem poczatku tile'a w zmiennej offset.
	 */
	void getObjectPositionAndTileOffset( Objects::GameObject* go, Vec2i& tile, Vec2f& offset );

	/*
	 * Odczepia obiekt od siatki Tili.
	 */
	void releaseTileHooks( Objects::GameObject* go ) {
		Level::Tile** hooks = go->getTileHooks();
		for( int i = 0; i < 4; ++i ) {
			if( hooks[ i ] != 0 ) {
				hooks[ i ]->unpin( go );
				hooks[ i ] = 0;
			}
		}
	}

	void attachObjectToTile( Objects::GameObject* go, Level::Tile** tile_hook, int x, int y );

	void installTileHooks( Objects::GameObject* go, const Vec2i& tile, const Vec2f& offset );

	/* Dokonuje testu przeciecia opartego na kolach. Jezeli przeciecie zachodzi, zwraca wektor odpowiadajacy
	 * nakladaniu sie 2 kol.
	 *
	 * UWAGA! Zwracany wektor okreslaja, jak przesunac cialo pierwsze tak, aby nie zachodzila kolizja z cialem
	 *        drugim. */
	bool intersects( Objects::GameObject* go1, Objects::GameObject* go2, Vec2f& overlap_vect ) {
		overlap_vect = go1->getPosition() - go2->getPosition();
		float overlap_dist = static_cast<float>( overlap_vect.magnitude() );
		float radius1 = go1->getRadius();
		float radius2 = go2->getRadius();
		if( radius1 + radius2 > overlap_dist ) {
			overlap_dist = radius1 + radius2 - overlap_dist;
			overlap_vect = overlap_vect.normalize();
			overlap_vect *= overlap_dist;
			return true;
		} else
			return false;
	}

	bool tileIsEnterable( int x, int y, Level::Tile::TileSide side ) {
		if( isValidTileCoord( x, y ) )
			return level_->getTile( x, y )->isEnterable( side );
		return false;
	}

public:

	Simulation( bool is_server );
	~Simulation();

	Objects::GameObject* getGameObject( Objects::GameObject::IdType id ) {
		if( id != ObjectId::INVALID_ID )
			return level_->getObject( id );
		else return 0;
	}

	Level::Level* getLevel() {
		return level_;
	}

	/*
	 * Zwraca czas, w jakim znajduje sie symulacja (nie jest to dokladny czas klienta!).
	 */
	long now() {
		return now_;
	}

	bool isServer() {
		return is_server_;
	}

	void initialize( Level::Level* level ) {
		level_ = level;
	}

	void updateObject( Objects::GameObject::IdType id, Data* data );

	void checkCollisions( Objects::GameObject* go );

	/*
	 * Odbiera i przetwarza oczekujace komendy.
	 */
	void receiveCommands();

	void simulate( long dt );

	/*
	 * Rozsyla dane do klientow.
	 */
	void broadcastData();
};

} //namespace GameLogic

#endif /* SIMULATION_H_ */
